/*
 * @(#)SOAPAddressHandler.java
 * Date 2013-11-17
 * Version 1.0
 * Author Jim Horner
 * Copyright (c)2012
 */


package us.hornerscorners.lamppost.ws;

import java.io.IOException;

import java.net.InetAddress;
import java.net.URL;

import java.util.Set;

import javax.servlet.ServletRequest;

import javax.xml.namespace.QName;

import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;

import javax.xml.ws.handler.MessageContext;

import javax.xml.ws.handler.MessageContext.Scope;

import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;

import org.apache.commons.lang3.StringUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


/**
 * Class description
 *
 *
 * @version        v1.0, 2013-11-17
 * @author         Jim Horner
 */
public class SOAPAddressHandler implements SOAPHandler<SOAPMessageContext> {

    /** Field description */
    public static final String ADDRESS_CONTEXT = "SOAPAddressHandler-ADDRESS_CONTEXT";

    /** Field description */
    private static final String ANONYMOUS =
        "http://www.w3.org/2005/08/addressing/anonymous";

    /** Field description */
    private static final String URI = "http://www.w3.org/2005/08/addressing";

    /** Field description */
    private final AddressCallback addressCallback;

    /** Field description */
    private final Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * Constructs ...
     *
     */
    public SOAPAddressHandler() {

        this(null);
    }

    /**
     * Constructs ...
     *
     *
     * @param addressCallback
     */
    public SOAPAddressHandler(AddressCallback addressCallback) {

        super();
        this.addressCallback = addressCallback;
    }

    /**
     *  Method description
     *
     *
     *  @param context
     */
    @Override
    public void close(MessageContext context) {

        // throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * Method description
     *
     *
     * @return
     */
    @Override
    public Set<QName> getHeaders() {

        return null;
    }

    /**
     * Method description
     *
     *
     * @param context
     *
     * @return
     */
    @Override
    public boolean handleFault(SOAPMessageContext context) {

        // throw new UnsupportedOperationException("Not supported yet.");
        return true;
    }

    /**
     * Method description
     *
     *
     * @param smc
     *
     * @return
     */
    @Override
    public boolean handleMessage(SOAPMessageContext smc) {

        SOAPMessage msg = smc.getMessage();

        try {

            Boolean outboundProperty =
                (Boolean) smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

            SOAPEnvelope envelope = msg.getSOAPPart().getEnvelope();
            SOAPHeader header = envelope.getHeader();

            if (header == null) {

                envelope.addHeader();
                header = envelope.getHeader();
            }

            AddressContext.Builder builder = new AddressContext.Builder();

            builder.replyTo(ANONYMOUS);

            ServletRequest request =
                (ServletRequest) smc.get(MessageContext.SERVLET_REQUEST);

            if (request != null) {

                builder.remoteIp(request.getRemoteAddr());
            }

            NodeList nodes = header.getElementsByTagNameNS(URI, "*");

            for (int i = 0; i < nodes.getLength(); ++i) {

                Node node = nodes.item(i);
                String name = node.getLocalName();

                switch (name) {

                    case "MessageID" :
                        builder.messageId(node.getTextContent());

                        break;

                    case "Action" :
                        builder.action(node.getTextContent());

                        break;

                    case "RelatesTo" :
                        builder.action(node.getTextContent());

                        break;

                    case "ReplyTo" :
                        NodeList repNodes = node.getChildNodes();

                        for (int j = 0; j < repNodes.getLength(); ++j) {

                            Node repNode = repNodes.item(j);
                            String repName = repNode.getLocalName();

                            logger.debug("ReplyTo-Name {}", repName);

                            if ("Address".equals(repName)) {

                                String replyTo = node.getTextContent();

                                builder.replyTo(replyTo);

                                if (Boolean.FALSE.equals(outboundProperty)
                                        && (request == null)) {

                                    builder.remoteIp(parseRemoteIp(replyTo));
                                }

                                break;
                            }
                        }

                        break;

                    case "To" :
                        String to = node.getTextContent();

                        builder.to(to);

                        if (Boolean.TRUE.equals(outboundProperty) && (request == null)) {

                            builder.remoteIp(parseRemoteIp(to));
                        }

                        break;
                }
            }

            AddressContext addressContext = builder.build();

            smc.put(ADDRESS_CONTEXT, addressContext);
            smc.setScope(ADDRESS_CONTEXT, Scope.APPLICATION);

            if (Boolean.TRUE.equals(outboundProperty)) {

                if (this.addressCallback != null) {
                    
                    this.addressCallback.setOutgoingContext(addressContext);
                }

                logger.debug("Outbound Address Context saved.");

            } else {

                if (this.addressCallback != null) {
                    
                    this.addressCallback.setIncomingContext(addressContext);
                }

                logger.debug("Inbound Address Context saved.");
            }

        } catch (Exception ex) {

            logger.debug("Error in the client handler", ex);
        }

        return true;
    }

    /**
     * Method description
     *
     *
     * @param str
     *
     * @return
     */
    private String parseRemoteIp(String str) {

        String result = null;

        if (StringUtils.isNotBlank(str)) {

            try {

                URL url = new URL(str);

                String host = url.getHost();
                InetAddress ip = InetAddress.getByName(host);

                result = ip.getHostAddress();

            } catch (IOException e) {

                logger.warn("Unable to set remoteIp from {}", str);
            }
        }

        return result;
    }
}
